#    Copyright 2023 Haotian Liu
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.


from typing import List, Optional, Tuple, Union

import torch
import torch.amp
import torch.nn as nn

from transformers import AutoConfig, AutoModelForCausalLM
from transformers import MambaConfig, MambaModel, MambaForCausalLM
from transformers.models.mamba.modeling_mamba import MambaCache

from transformers.modeling_outputs import CausalLMOutputWithPast
from transformers.generation.utils import GenerateOutput

from ..llava_arch import LlavaMetaModel, LlavaMetaForCausalLM


class LlavaMambaConfig(MambaConfig):
    model_type = "llava_mamba"

class LlavaMambaModel(LlavaMetaModel, MambaModel):
    config_class = LlavaMambaConfig

    def __init__(self, config: MambaConfig):
        super(LlavaMambaModel, self).__init__(config)

    def embed_tokens(self, x):
        return self.embeddings(x)


class LlavaMambaForCausalLM(MambaForCausalLM, LlavaMetaForCausalLM):
    config_class = LlavaMambaConfig

    def __init__(self, config):
        super(MambaForCausalLM, self).__init__(config)
        self.backbone = LlavaMambaModel(config)
        self.vocab_size = config.vocab_size
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)

        # Initialize weights and apply final processing
        self.post_init()

    def get_model(self):
        return self.backbone

    def forward(
        self,
        input_ids: torch.LongTensor = None,
        inputs_embeds: Optional[torch.FloatTensor] = None,
        # attention_mask of MLLM based on Mamba is used for getting rid of padding tokens when NEFTuning
        attention_mask: Optional[torch.Tensor] = None,  
        cache_params: Optional[MambaCache] = None, 
        labels: Optional[torch.LongTensor] = None,
        use_cache: Optional[bool] = None,
        output_hidden_states: Optional[bool] = None,
        images: Optional[torch.FloatTensor] = None,
        image_sizes: Optional[List[List[int]]] = None,
        return_dict: Optional[bool] = None,
        **kwargs
    ) -> Union[Tuple, CausalLMOutputWithPast]:

        # My additions
        with torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=True if self.training else False):
            if inputs_embeds is None:
                (
                    input_ids,
                    _,
                    _,
                    _,
                    inputs_embeds,
                    labels
                ) = self.prepare_inputs_labels_for_multimodal(
                    input_ids,
                    None,  # `position_ids` no use in Mamba
                    attention_mask, 
                    None,  # `past_key_values` no use in Mamba
                    labels,
                    images,
                    image_sizes
                )

            return super().forward(
                input_ids=input_ids,
                inputs_embeds=inputs_embeds,
                cache_params=cache_params,
                labels=labels,
                use_cache=use_cache,
                output_hidden_states=output_hidden_states,
                return_dict=return_dict
            )

    @torch.no_grad()
    def generate(
        self,
        inputs: Optional[torch.Tensor] = None,
        images: Optional[torch.Tensor] = None,
        image_sizes: Optional[torch.Tensor] = None,
        **kwargs,
    ) -> Union[GenerateOutput, torch.LongTensor]:
        if "inputs_embeds" in kwargs:
            raise NotImplementedError("`inputs_embeds` is not supported")

        if images is not None:
            (
                inputs,
                _,
                _,
                _,
                inputs_embeds,
                _
            ) = self.prepare_inputs_labels_for_multimodal(
                inputs,
                None,  # `position_ids` no use for Mamba
                None,  # `attention_mask` no use for Mamba
                None,  # `past_key_values` no use for Mamba
                None,  # `labels` no use in generationo phase
                images,
                image_sizes=image_sizes
            )
        else:
            inputs_embeds = self.get_model().embed_tokens(inputs)

        return super().generate(
            inputs_embeds=inputs_embeds,
            **kwargs
        )

    def prepare_inputs_for_generation(self, input_ids, inputs_embeds=None, **kwargs):
        images = kwargs.pop("images", None)
        image_sizes = kwargs.pop("image_sizes", None)
        model_inputs = super().prepare_inputs_for_generation(
            input_ids, inputs_embeds=inputs_embeds, **kwargs
        )
        if images is not None:
            model_inputs['images'] = images
        if image_sizes is not None:
            model_inputs['image_sizes'] = image_sizes
        return model_inputs

AutoConfig.register("llava_mamba", LlavaMambaConfig)
AutoModelForCausalLM.register(LlavaMambaConfig, LlavaMambaForCausalLM)
